package fun.yizhierha.operation.service.impl;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import fun.yizhierha.common.base.BaseErrDto;
import fun.yizhierha.common.exception.BadRequestException;
import fun.yizhierha.common.utils.PageUtils;
import fun.yizhierha.common.utils.Query;
import fun.yizhierha.common.utils.SecurityUtils;
import fun.yizhierha.common.utils.ValidList;
import fun.yizhierha.common.utils.file.ExcelUtils;
import fun.yizhierha.common.utils.file.FileUtil;
import fun.yizhierha.operation.domain.*;
import fun.yizhierha.operation.domain.vo.CreateOraDeployVo;
import fun.yizhierha.operation.domain.vo.UpdateOraDeployVo;
import fun.yizhierha.operation.domain.vo.RetrieveOraDeployVo;
import fun.yizhierha.operation.mapper.OraDeployMapper;
import fun.yizhierha.operation.service.OraDeployHistoryService;
import fun.yizhierha.operation.service.OraDeployServerService;
import fun.yizhierha.operation.service.OraServerService;
import fun.yizhierha.operation.service.mapstruct.OraDeployMapstruct;
import fun.yizhierha.operation.service.OraDeployService;
import fun.yizhierha.operation.util.ExecuteShellUtil;
import fun.yizhierha.operation.util.ScpClientUtil;
import fun.yizhierha.operation.websocket.MsgType;
import fun.yizhierha.operation.websocket.SocketMsg;
import fun.yizhierha.operation.websocket.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.IOException;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.*;
import javax.servlet.http.HttpServletResponse;

/**
 * generated by EH-Admin
 *
 * @author xaopohi
 * @date Wed Dec 21 15:42:52 CST 2022
 **/
@Slf4j
@Service
@RequiredArgsConstructor
public class OraDeployServiceImpl extends ServiceImpl<OraDeployMapper, OraDeploy> implements OraDeployService {

    private final OraDeployMapstruct oraDeployMapstruct;

    private static final SimpleDateFormat format = new SimpleDateFormat("yyyyMMddhhmmss");
    private static final String FILE_SEPARATOR = "/";

    @Autowired
    OraServerService serverDeployService;
    @Autowired
    OraDeployServerService oraDeployServerService;
    @Autowired
    OraDeployHistoryService oraDeployHistoryService;
    private final Integer count = 30;

    @Override
    public PageUtils<OraDeploy> list(RetrieveOraDeployVo retrieveOraDeployVo, Query.PageVo pageVo) {

        IPage<OraDeploy> iPage = baseMapper.selectByRtVo(new Query<OraDeploy>().getPage(pageVo), retrieveOraDeployVo);

        return new PageUtils<>(iPage);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public synchronized void save(CreateOraDeployVo createOraDeployVo) {
        // 1.字段为UNI，需要不重复
        UserDetails currentUser = SecurityUtils.getCurrentUser();
        // 2.映射数据
        OraDeploy oraDeploy = oraDeployMapstruct.toOraDeploy(createOraDeployVo);
        oraDeploy.setCreateTime(new Timestamp(new Date().getTime()));
        oraDeploy.setCreateBy(currentUser.getUsername());
        // 3.保存
        this.save(oraDeploy);
        QueryWrapper<OraDeploy> wrapper = new QueryWrapper<>();
        Long appId = createOraDeployVo.getAppId();

        if (appId != null) {
            wrapper.like(OraDeploy.COL_APP_ID, appId);
        }
        wrapper.orderBy(true, false, "create_time");
        OraDeploy one = this.list(wrapper).get(0);
        List<OraDeployServer> saves = new ArrayList<>();
        for (int i = 0; i < createOraDeployVo.getServerId().size(); i++) {
            OraDeployServer oraDeployServer = new OraDeployServer();
            oraDeployServer.setDeployId(one.getId());
            oraDeployServer.setServerId(createOraDeployVo.getServerId().get(i));
            saves.add(oraDeployServer);
        }

        oraDeployServerService.save(saves);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public synchronized void edit(ValidList<UpdateOraDeployVo> updateOraDeployVoList, List<BaseErrDto> errDtoList) {
        List<OraDeploy> toUpdateOraDeployList = new ArrayList<>();
        List<OraDeployServer> toSaveDeployServerList = new ArrayList<>();
        List<Long> toDelDeployServerIdList = new ArrayList<>();
        for (UpdateOraDeployVo updateOraDeployVo : updateOraDeployVoList) {
            Long id = updateOraDeployVo.getId();
            List<Long> serverIdList = updateOraDeployVo.getServerId();
            // 1.字段为UNI，需要不重复
            UserDetails currentUser = SecurityUtils.getCurrentUser();

            if (serverIdList != null && !serverIdList.isEmpty()){
                toDelDeployServerIdList.add(id);
                for (int i = 0; i < updateOraDeployVo.getServerId().size(); i++) {
                    OraDeployServer oraDeployServer = new OraDeployServer();
                    oraDeployServer.setDeployId(id);
                    oraDeployServer.setServerId(updateOraDeployVo.getServerId().get(i));
                    toSaveDeployServerList.add(oraDeployServer);
                }
                OraDeploy oraDeploy = oraDeployMapstruct.toOraDeploy(updateOraDeployVo);
                oraDeploy.setUpdateTime(new Timestamp(new Date().getTime()));
                oraDeploy.setUpdateBy(currentUser.getUsername());
                toUpdateOraDeployList.add(oraDeploy);
            }else {
                BaseErrDto baseErrDto = new BaseErrDto();
                baseErrDto.setId(id);
                baseErrDto.setErrorVal("[]");
                baseErrDto.setErrorField("serverId");
                baseErrDto.setErrorMsg("服务器至少有一个！");
                errDtoList.add(baseErrDto);
            }
        }

        // 2.更新
        if(!toDelDeployServerIdList.isEmpty()){
            this.updateBatchById(toUpdateOraDeployList);
            oraDeployServerService.remove(new QueryWrapper<OraDeployServer>().in(OraDeployServer.COL_DEPLOY_ID,toDelDeployServerIdList));
            oraDeployServerService.save(toSaveDeployServerList);
        }
    }

    @Override
    public void remove(Set<Long> ids) {
        this.removeByIds(ids);
    }

    @Override
    public void download(HttpServletResponse response) {
        ExcelUtils.export(response, "部署管理信息表", this.list(), OraDeploy.class);
    }

    @Override
    public void deploy(String dir, Long id) {
        deployApp(dir, id);
    }

    private void deployApp(String dir, Long id) {
        OraDeploy deploy = getDeployById(id);
        OraApp app = deploy.getApp();
        if (app == null) {
            sendMsg("包对应应用信息不存在", MsgType.ERROR);
            throw new BadRequestException("包对应应用信息不存在");
        }
        int port = app.getPort();
        //这个是服务器部署路径
        String uploadPath = app.getUploadPath();
        StringBuilder sb = new StringBuilder();
        String msg;
        List<OraServer> deploys = deploy.getServer();//deploy.getServers();
        for (OraServer deployDTO : deploys) {
            String ip = deployDTO.getIp();
            ExecuteShellUtil executeShellUtil = getExecuteShellUtil(ip);
            //判断是否第一次部署
            boolean flag = checkFile(executeShellUtil, app);
            //第一步要确认服务器上有这个目录
            executeShellUtil.execute("mkdir -p " + app.getUploadPath());
            executeShellUtil.execute("mkdir -p " + app.getBackupPath());
            executeShellUtil.execute("mkdir -p " + app.getDeployPath());
            //上传文件
            msg = String.format("登陆到服务器:%s", ip);
            ScpClientUtil scpClientUtil = getScpClientUtil(ip);
            log.info(msg);
            sendMsg(msg, MsgType.INFO);
            msg = String.format("上传文件到服务器:%s目录:%s下，请稍等...", ip, uploadPath);
            sendMsg(msg, MsgType.INFO);
            scpClientUtil.putFile(dir, uploadPath);
            if (flag) {
                sendMsg("停止原来应用", MsgType.INFO);
                //停止应用
                stopApp(port, executeShellUtil);
                sendMsg("备份原来应用", MsgType.INFO);
                //备份应用
                File file = new File(dir);
                backupApp(file.getName(),executeShellUtil, ip, app.getDeployPath() + FILE_SEPARATOR+file.getName(), app.getName(), app.getBackupPath() + FILE_SEPARATOR, id);
            }
            sendMsg("部署应用", MsgType.INFO);
            //部署文件,并启动应用
            String deployScript = app.getDeployScript();
            sendMsg("应用部署中，请耐心等待部署结果，或者稍后手动查看部署状态", MsgType.INFO);
            executeShellUtil.execute(deployScript);
            sleep(3);
            sendMsg("应用启动中，请耐心等待启动结果，或者稍后手动查看启动状态", MsgType.INFO);
            executeShellUtil.executeServer(app.getStartScript());
            int i = 0;
            boolean result = false;

            if (executeShellUtil.getSession().isConnected()){
                // 由于启动应用需要时间，所以需要循环获取状态，如果超过30次，则认为是启动失败
                while (i++ < count) {
                    result = checkIsRunningStatus(port, executeShellUtil);
                    if (result) {
                        break;
                    }
                    // 休眠6秒
                    sleep(6);
                }
            }

            sb.append("服务器:").append(deployDTO.getName()).append("应用:").append(app.getName());
            sendResultMsg(result, sb);
            executeShellUtil.close();
            File file = new File(dir);
            FileUtil.rename(file, (format.format(new Date()) + file.getName()), true);
        }
    }

    private void sendResultMsg(boolean result, StringBuilder sb) {
        if (result) {
            sb.append("启动成功!");
            sendMsg(sb.toString(), MsgType.INFO);
        } else {
            sb.append("启动失败!");
            sendMsg(sb.toString(), MsgType.ERROR);
        }
    }

    private boolean checkIsRunningStatus(int port, ExecuteShellUtil executeShellUtil) {
        String result = executeShellUtil.executeForResult(String.format("fuser -n tcp %d", port));
        return result.indexOf("/tcp:") > 0;
    }

    private void sleep(int second) {
        try {
            Thread.sleep(second * 1000);
        } catch (InterruptedException e) {
            log.error(e.getMessage(), e);
        }
    }

    private void backupApp(String filename, ExecuteShellUtil executeShellUtil, String ip, String fileSavePath, String appName, String backupPath, Long id) {
        Date date = new Date();
        String deployDate = DateUtil.format(date, DatePattern.PURE_DATETIME_PATTERN);
        StringBuilder sb = new StringBuilder();
        backupPath += appName + FILE_SEPARATOR + deployDate + "\n";
        sb.append("mkdir -p ").append(backupPath);
        sb.append("cp ").append(fileSavePath);
        sb.append(" ").append(backupPath);
        //.append(appName)
        log.info("备份应用脚本:" + sb.toString());
        executeShellUtil.execute(sb.toString());
        //还原信息入库
        OraDeployHistory deployHistory = new OraDeployHistory();
        deployHistory.setAppName(appName);
        deployHistory.setDeployUser(SecurityUtils.getCurrentUsername());
        deployHistory.setIp(ip);
        deployHistory.setDeployId(id);
        deployHistory.setDeployDate(DateUtil.date(date).toTimestamp());
        deployHistory.setFileName(filename);
        oraDeployHistoryService.save(deployHistory);
    }

    /**
     * 停止应用程序
     *
     * @param port             端口
     * @param executeShellUtil 执行shell跑龙套
     */
    private void stopApp(int port, ExecuteShellUtil executeShellUtil) {
        //发送停止命令
        executeShellUtil.execute(String.format("lsof -i :%d|grep -v \"PID\"|awk '{print \"kill -9\",$2}'|sh", port));

    }

    private ScpClientUtil getScpClientUtil(String ip) {
        OraServer serverDeployDTO = serverDeployService.findByIp(ip);
        if (serverDeployDTO == null) {
            sendMsg("IP对应服务器信息不存在：" + ip, MsgType.ERROR);
            throw new BadRequestException("IP对应服务器信息不存在：" + ip);
        }
        return ScpClientUtil.getInstance(ip, serverDeployDTO.getPort(), serverDeployDTO.getAccount(), serverDeployDTO.getPassword());
    }

    private boolean checkFile(ExecuteShellUtil executeShellUtil, OraApp appDTO) {
        String result = executeShellUtil.executeForResult("find " + appDTO.getDeployPath() + " -name " + appDTO.getName());
        return !result.contains("No such file or directory");
    }

    private ExecuteShellUtil getExecuteShellUtil(String ip) {
        OraServer serverDeployDTO = serverDeployService.findByIp(ip);
        if (serverDeployDTO == null) {
            sendMsg("IP对应服务器信息不存在：" + ip, MsgType.ERROR);
            throw new BadRequestException("IP对应服务器信息不存在：" + ip);
        }
        return new ExecuteShellUtil(ip, serverDeployDTO.getAccount(), serverDeployDTO.getPassword(), serverDeployDTO.getPort());
    }

    private void sendMsg(String msg, MsgType msgType) {
        try {
            WebSocketServer.sendInfo(new SocketMsg(msg, msgType), SecurityUtils.getCurrentUsername());
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public String serverReduction(Long deployHisId) {
        if (deployHisId == null) throw new BadRequestException("deployHisId不能为空！");
        OraDeployHistory oraDeployHistory = oraDeployHistoryService.getById(deployHisId);
        if (oraDeployHistory == null) throw new BadRequestException("deployHisId = "+deployHisId+" = null");
        OraDeploy deployInfo = getDeployById(oraDeployHistory.getDeployId());
        String deployDate = DateUtil.format(oraDeployHistory.getDeployDate(), DatePattern.PURE_DATETIME_PATTERN);
        OraApp app = deployInfo.getApp();
        if (app == null) {
            sendMsg("应用信息不存在：" + oraDeployHistory.getAppName(), MsgType.ERROR);
            throw new BadRequestException("应用信息不存在：" + oraDeployHistory.getAppName());
        }
        String backupPath = app.getBackupPath()+FILE_SEPARATOR;
        backupPath += oraDeployHistory.getAppName() + FILE_SEPARATOR + deployDate;
        //这个是服务器部署路径
        String deployPath = app.getDeployPath();
        String ip = oraDeployHistory.getIp();
        ExecuteShellUtil executeShellUtil = getExecuteShellUtil(ip);
        String msg;

        msg = String.format("登陆到服务器:%s", ip);
        log.info(msg);
        sendMsg(msg, MsgType.INFO);
        sendMsg("停止原来应用", MsgType.INFO);
        //停止应用
        stopApp(app.getPort(), executeShellUtil);
        //删除原来应用
        sendMsg("删除应用", MsgType.INFO);
        executeShellUtil.execute("rm -rf " + deployPath + FILE_SEPARATOR + oraDeployHistory.getFileName());
        //还原应用
        sendMsg("还原应用", MsgType.INFO);
        executeShellUtil.execute("cp -r " + backupPath + "/. " + deployPath);
        sendMsg("启动应用", MsgType.INFO);
        executeShellUtil.executeServer(app.getStartScript());
        sendMsg("应用启动中，请耐心等待启动结果，或者稍后手动查看启动状态", MsgType.INFO);
        int i  = 0;
        boolean result = false;
        if (executeShellUtil.getSession().isConnected()){
            // 由于启动应用需要时间，所以需要循环获取状态，如果超过30次，则认为是启动失败
            while (i++ < count){
                result = checkIsRunningStatus(app.getPort(), executeShellUtil);
                if(result){
                    break;
                }
                // 休眠6秒
                sleep(6);
            }
        }
        StringBuilder sb = new StringBuilder();
        sb.append("服务器:").append(ip).append("应用:").append(oraDeployHistory.getAppName());
        sendResultMsg(result, sb);
        executeShellUtil.close();
        return "";
    }

    @Override
    public String serverStatus(Long deployId) {
        OraDeploy resources = getDeployById(deployId);
        List<OraServer> serverDeploys = resources.getServer();
        OraApp app = resources.getApp();
        for (OraServer serverDeploy : serverDeploys) {
            StringBuilder sb = new StringBuilder();
            ExecuteShellUtil executeShellUtil = getExecuteShellUtil(serverDeploy.getIp());
            sb.append("服务器:").append(serverDeploy.getName()).append("应用:").append(app.getName()).append(" ");
            boolean result = checkIsRunningStatus(app.getPort(), executeShellUtil);
            if (result) {
                sb.append("正在运行");
                sendMsg(sb.toString(), MsgType.INFO);
            } else {
                sb.append("已停止!");
                sendMsg(sb.toString(), MsgType.ERROR);
            }
            log.info(sb.toString());
            executeShellUtil.close();
        }
        return "执行完毕";
    }

    private OraDeploy getDeployById(Long deployId) {
        if (deployId == null) throw new BadRequestException("deployId不能为空");
        OraDeploy resources = this.getById(deployId);
        if (resources == null) throw new BadRequestException("deployId="+deployId+"不存在");
        return resources;
    }

    @Override
    public String startServer(Long deployId) {
        OraDeploy resources = getDeployById(deployId);
        List<OraServer> server = resources.getServer();
        OraApp app = resources.getApp();
        for (OraServer deploy : server) {
            StringBuilder sb = new StringBuilder();
            ExecuteShellUtil executeShellUtil = getExecuteShellUtil(deploy.getIp());
            //为了防止重复启动，这里先停止应用
            stopApp(app.getPort(), executeShellUtil);
            sb.append("服务器[").append(deploy.getIp()).append("]:").append(deploy.getName()).append(" 应用:").append(app.getName())
                    .append(" ")
            ;
            sendMsg("服务器["+deploy.getIp()+"]开始执行脚本", MsgType.INFO);
            executeShellUtil.executeServer(app.getStartScript());
            sendMsg("应用启动中，请耐心等待启动结果，或者稍后手动查看运行状态", MsgType.INFO);
            int i  = 0;
            boolean result = false;

            if (executeShellUtil.getSession().isConnected()){
                // 由于启动应用需要时间，所以需要循环获取状态，如果超过30次，则认为是启动失败
                while (i++ < count){
                    result = checkIsRunningStatus(app.getPort(), executeShellUtil);
                    if(result){
                        break;
                    }
                    // 休眠6秒
                    sleep(6);
                }
            }
            else {
                sb.append("原因：服务器连接失败 ");
            }
            sendResultMsg(result, sb);
            log.info(sb.toString());
            executeShellUtil.close();
        }
        return "执行完毕";
    }

    @Override
    public String stopServer(Long deployId) {
        OraDeploy resources = getDeployById(deployId);
        List<OraServer> server = resources.getServer();
        OraApp app = resources.getApp();
        for (OraServer deploy : server) {
            StringBuilder sb = new StringBuilder();
            ExecuteShellUtil executeShellUtil = getExecuteShellUtil(deploy.getIp());
            sb.append("服务器:").append(deploy.getName()).append("应用:").append(app.getName()).append(" ");
            sendMsg("下发停止命令", MsgType.INFO);
            //停止应用
            stopApp(app.getPort(), executeShellUtil);
            sleep(1);
            boolean result = checkIsRunningStatus(app.getPort(), executeShellUtil);
            if (result) {
                sb.append("关闭失败!");
                sendMsg(sb.toString(), MsgType.ERROR);
            } else {
                sb.append("关闭成功!");
                sendMsg(sb.toString(), MsgType.INFO);
            }
            log.info(sb.toString());
            executeShellUtil.close();
        }
        return "执行完毕";
    }

}